module SingleCycleRV32I (
    input clk,
    input reset,
    input [31:0] instruction,
    input [31:0] read_from_memory,
    output  [31:0] instruction_address,
    output  [31:0] memory_address,
    output  reg [31:0] memory_write_data,
    output   write_enable,
    output  reg [3:0] write_mask,
);
    reg [31:0] registers [31:0];
    reg [31:0] PC;
    reg [4:0] rd, rs1, rs2;
    reg [2:0] funct3;
    reg [6:0] opcode;
    reg [31:0] imm_I, imm_S, imm_B, imm_U, imm_J;
    reg [6:0] funct7;
    reg [31:0] alu_result;
    reg [31:0] branch_target;
    reg [31:0] jump_target;
    reg branch_taken;
    reg alu_add,alu_sub,alu_and,alu_or,alu_xor,alu_sll,alu_srl,alu_sra,alu_lui,alu_auipc;
    reg branch,mem_read,mem_write,jump,reg_write;
    reg [31:0] read_data;
    
    always @(*) begin
        opcode = instruction[6:0];
        rd = instruction[11:7];
        rs1 = instruction[19:15];
        rs2 = instruction[24:20];
        funct3 = instruction[14:12];
        funct7 = instruction[31:25];
        imm_I = { { 20 { instruction[31] } }, instruction[31:20] };
        imm_S = { { 20 { instruction[31] } }, instruction[31:25], instruction[11:7] };
        imm_B = { { 19 { instruction[31] } }, instruction[31],instruction[7], instruction[30:25], instruction[11:8], 1'b0 };
        imm_U = { instruction[31:12], 12'b0 };
        imm_J = { { 12 { instruction[31] } }, instruction[19:12], instruction[20], instruction[30:21], 1'b0 };
    end
    
    always @(*) begin
        alu_add = (opcode == 7'b0010011 && funct3 == 3'b000) || (opcode == 7'b0110011 && funct3 == 3'b000 && funct7 == 7'b0000000);
        alu_sub = (opcode == 7'b0110011 && funct3 == 3'b000 && funct7 == 7'b0100000);
        alu_and = (opcode == 7'b0010011 && funct3 == 3'b111) || (opcode == 7'b0110011 && funct3 == 3'b111);
        alu_or = (opcode == 7'b0010011 && funct3 == 3'b110) || (opcode == 7'b0110011 && funct3 == 3'b110);
        alu_xor = (opcode == 7'b0010011 && funct3 == 3'b100) || (opcode == 7'b0110011 && funct3 == 3'b100);
        alu_sll = (opcode == 7'b0010011 && funct3 == 3'b001) || (opcode == 7'b0110011 && funct3 == 3'b001);
        alu_srl = (opcode == 7'b0010011 && funct3 == 3'b101 && funct7 == 7'b0000000) || (opcode == 7'b0110011 && funct3 == 3'b101 && funct7 == 7'b0000000);
        alu_sra = (opcode == 7'b0010011 && funct3 == 3'b101 && funct7 == 7'b0100000) || (opcode == 7'b0110011 && funct3 == 3'b101 && funct7 == 7'b0100000);
        alu_lui = (opcode == 7'b0110111);
        alu_auipc=(opcode == 7'b0010111);
    end

    always @(*) begin
        if (alu_add) alu_result = registers[rs1] + (opcode == 7'b0010011 ? imm_I : registers[rs2]);
        else if (alu_sub) alu_result = registers[rs1] - registers[rs2];
        else if (alu_and) alu_result = registers[rs1] & (opcode == 7'b0010011 ? imm_I : registers[rs2]);
        else if (alu_or) alu_result = registers[rs1] | (opcode == 7'b0010011 ? imm_I : registers[rs2]);
        else if (alu_xor) alu_result = registers[rs1] ^ (opcode == 7'b0010011 ? imm_I : registers[rs2]);
        else if (alu_sll) alu_result = registers[rs1] << (opcode == 7'b0010011 ? imm_I[4:0] : registers[rs2][4:0]);
        else if (alu_srl) alu_result = registers[rs1] >> (opcode == 7'b0010011 ? imm_I[4:0] : registers[rs2][4:0]);
        else if (alu_sra) alu_result = registers[rs1] >>> (opcode == 7'b0010011 ? imm_I[4:0] : registers[rs2][4:0]);
        else if (alu_lui) alu_result = imm_U;
        else if (alu_auipc) alu_result=PC+imm_U;
        else alu_result = 32'h0;
    end

    always @(*) begin
        branch = (opcode == 7'b1100011);
        mem_read = (opcode == 7'b0000011);
        mem_write = (opcode == 7'b0100011);
        jump = (opcode == 7'b1101111) || (opcode == 7'b1100111);
        reg_write = (opcode == 7'b0110011) || (opcode == 7'b0010011) || (opcode == 7'b0000011) || (opcode == 7'b1101111) || (opcode == 7'b1100111) || (opcode == 7'b0110111) || (opcode == 7'b0010111);
    end
    wire [31:0]read_data_1=registers[rs1];
    wire [31:0]read_data_2=registers[rs2];
    always @(*) begin
        branch_target = PC + imm_B;
        jump_target = (opcode == 7'b1101111) ? (PC + imm_J) : (read_data_1+ imm_I);
    end

    always @(*) begin
        branch_taken = 0;
        if (branch) begin
            case (funct3)
                3'b000: branch_taken = (registers[rs1] == registers[rs2]);
                3'b001: branch_taken = (registers[rs1] != registers[rs2]);
                3'b100: branch_taken = ($signed(registers[rs1]) < $signed(registers[rs2]));
                3'b101: branch_taken = ($signed(registers[rs1]) >= $signed(registers[rs2]));
                3'b110: branch_taken = (registers[rs1] < registers[rs2]);
                3'b111: branch_taken = (registers[rs1] >= registers[rs2]);
                default: branch_taken = 0;
            endcase
        end
    end
    always @(posedge clk) begin
        if (reset) begin
            PC <= 32'h10000;
            for (integer i = 0; i < 32; i = i + 1) registers[i] <= (i==2)?32'h10000:32'h0;
        end else begin
            if (branch_taken) PC <= branch_target;
            else if (jump) PC <= jump_target;
            else PC <= PC + 4;

            if (reg_write && rd != 5'h0) begin
                if (opcode == 7'b0000011) registers[rd] <= read_data;
                else if (opcode == 7'b1101111 || opcode == 7'b1100111) registers[rd] <= PC + 4;
                else registers[rd] <= alu_result;
            end
        end
    end

    always @(*) begin
        write_mask = 4'b0000;
        memory_write_data = 32'd0;
        read_data = 32'd0;
        if (mem_read) begin
            case (funct3)
                3'd0: 
                    read_data = {{24{read_from_memory[memory_address[1:0]*8+7]}}, read_from_memory[memory_address[1:0]*8+:8]};
                3'd1: 
                    read_data = {{16{read_from_memory[memory_address[1:0]*8+15]}}, read_from_memory[memory_address[1:0]*8+:16]};
                3'd2: 
                    read_data = read_from_memory;
                3'd4: 
                    read_data = {24'd0, read_from_memory[memory_address[1:0]*8+:8]};
                3'd5: 
                    read_data = {16'd0, read_from_memory[memory_address[1:0]*8+:16]};
            endcase
        end
        if (mem_write) begin
            case (funct3)
                3'd0: 
                    begin
                        memory_write_data = {24'd0, registers[rs2][7:0]};
                        write_mask = 4'b0001 << memory_address[1:0];
                    end
                3'd1: 
                    begin
                        memory_write_data = {16'd0, registers[rs2][15:0]};
                        write_mask = 4'b0011 << (memory_address[1:0] * 2);
                    end
                3'd2: 
                    begin
                        memory_write_data = registers[rs2];
                        write_mask = 4'b1111;
                    end
            endcase
        end
    end


    assign instruction_address = PC;
    assign memory_address = (mem_write || mem_read) ? (registers[rs1] + (mem_write ? imm_S : imm_I)) : 32'h0;
    assign write_enable = mem_write;
endmodule
